﻿using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Polly;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    public class S3BucketFileHelper2
    {

        //private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

        private readonly IAmazonS3 _s3Client;
        private const long MultipartThreshold = 4L * 1024 * 1024 * 1024; // 4GB
        private const int MaxRetries = 3;
        private readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(2);
        private const int PartSizeMB = 100; // 分段大小


        //下载进度相关
        //发控制字段
        private readonly SemaphoreSlim _downloadSemaphore = new SemaphoreSlim(4, 4); // 最大并发数4
        private const int DownloadBufferSize = 81920; // 80KB缓冲区


        private long _totalDownloadedBytes;
        private long _totalFileSize;
        private long _totalMergedBytes;
        private const long DownloadMultipartThreshold = 100 * 1024 * 1024; // 100MB
        private const int DownloadPartSizeMB = 20; // 分段大小20MB



        public event Action<double> ProgressChanged;


        // 下载进度
        public event Action<TransferProgress> DownloadProgressChanged; // 统一进度事件

        public enum TransferPhase
        {
            Downloading,
            Merging
        }

        public class TransferProgress
        {
            public TransferPhase Phase { get; set; }
            public double Percentage { get; set; }
            public string Status { get; set; }
        }

        public event Action OnFinish;

        public S3BucketFileHelper2(IAmazonS3 s3Client)
        {
            _s3Client = s3Client;
        }


        #region 移动和删除
        public async Task MoveFileAsync(string bucketName, string sourceKey, string destKey)
        {
            try
            {
                // 获取文件元数据
                var metadata = await GetObjectMetadata(bucketName, sourceKey);

                // 自动选择传输方式
                if (metadata.ContentLength > MultipartThreshold)
                {
                    await MoveLargeFile(bucketName, sourceKey, destKey, metadata.ContentLength);
                }
                else
                {
                    var copyResult = await MoveSmallFile(bucketName, sourceKey, destKey);
                }


                // 删除源文件
                var delResult = await DeleteSourceFile(bucketName, sourceKey);

                OnFinish?.Invoke();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private async Task MoveLargeFile(
            string bucketName,
            string sourceKey,
            string destKey,
            long fileSize)
        {
            string uploadId = null;
            try
            {
                // 初始化分段上传
                uploadId = await InitiateMultipartUpload(bucketName, destKey);

                // 计算分段参数
                var partSize = PartSizeMB * 1024 * 1024;
                var partCount = (int)Math.Ceiling((double)fileSize / partSize);

                // 并行复制分段
                var copyTasks = new List<Task<CopyPartResponse>>();
                for (var partNumber = 1; partNumber <= partCount; partNumber++)
                {
                    var firstByte = (partNumber - 1) * partSize;
                    var lastByte = Math.Min(firstByte + partSize - 1, fileSize - 1);

                    copyTasks.Add(CopyPartWithRetryAsync(
                        bucketName,
                        sourceKey,
                        destKey,
                        uploadId,
                        partNumber,
                        firstByte,
                        lastByte));
                }

                var responses = await Task.WhenAll(copyTasks);

                // 完成上传
                await CompleteMultipartUpload(bucketName, destKey, uploadId, responses);

                // 更新进度
                ProgressChanged?.Invoke(100);
            }
            catch
            {
                if (uploadId != null)
                {
                    await AbortMultipartUpload(bucketName, destKey, uploadId);
                }
                throw;
            }
        }

        private async Task<string> InitiateMultipartUpload(string bucketName, string key)
        {
            var request = new InitiateMultipartUploadRequest
            {
                BucketName = bucketName,
                Key = key,
                StorageClass = S3StorageClass.Standard,
                ServerSideEncryptionMethod = ServerSideEncryptionMethod.AES256
            };

            var response = await _s3Client.InitiateMultipartUploadAsync(request);
            return response.UploadId;
        }

        private async Task<CopyPartResponse> CopyPartWithRetryAsync(
            string bucketName,
            string sourceKey,
            string destKey,
            string uploadId,
            int partNumber,
            long firstByte,
            long lastByte)
        {
            var policy = Polly.Policy
                .Handle<AmazonS3Exception>(ex =>
                    ex.ErrorCode == "RequestTimeout" ||
                    ex.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
                .WaitAndRetryAsync(MaxRetries, _ => RetryDelay);

            return await policy.ExecuteAsync(async () =>
            {
                var response = await _s3Client.CopyPartAsync(new CopyPartRequest
                {
                    SourceBucket = bucketName,
                    SourceKey = sourceKey,
                    DestinationBucket = bucketName,
                    DestinationKey = destKey,
                    UploadId = uploadId,
                    PartNumber = partNumber,
                    FirstByte = firstByte,
                    LastByte = lastByte
                });

                UpdateProgress(partNumber, (int)Math.Ceiling((double)(lastByte + 1) / PartSizeMB));
                return response;
            });
        }

        private async Task CompleteMultipartUpload(
            string bucketName,
            string key,
            string uploadId,
            IEnumerable<CopyPartResponse> responses)
        {
            var request = new CompleteMultipartUploadRequest
            {
                BucketName = bucketName,
                Key = key,
                UploadId = uploadId,
                PartETags = responses.Select(r => new PartETag(r.PartNumber, r.ETag)).ToList()
            };

            await _s3Client.CompleteMultipartUploadAsync(request);
        }

        private async Task<CopyObjectResponse> MoveSmallFile(string bucketName, string sourceKey, string destKey)
        {
            return await _s3Client.CopyObjectAsync(new CopyObjectRequest
            {
                SourceBucket = bucketName,
                SourceKey = sourceKey,
                DestinationBucket = bucketName,
                DestinationKey = destKey,
                MetadataDirective = S3MetadataDirective.COPY
            });
        }

        public async Task<DeleteObjectResponse> DeleteSourceFile(string bucketName, string key)
        {
            return await _s3Client.DeleteObjectAsync(new DeleteObjectRequest
            {
                BucketName = bucketName,
                Key = key
            });
        }

        private async Task<GetObjectMetadataResponse> GetObjectMetadata(
            string bucketName, string key)
        {
            return await _s3Client.GetObjectMetadataAsync(new GetObjectMetadataRequest
            {
                BucketName = bucketName,
                Key = key
            });
        }

        private async Task AbortMultipartUpload(
            string bucketName,
            string key,
            string uploadId)
        {
            try
            {
                await _s3Client.AbortMultipartUploadAsync(new AbortMultipartUploadRequest
                {
                    BucketName = bucketName,
                    Key = key,
                    UploadId = uploadId
                });
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void UpdateProgress(int completedPart, int totalParts)
        {
            var progress = (double)completedPart / totalParts * 100;
            ProgressChanged?.Invoke(Math.Round(progress, 2));
        }

        #endregion


        #region 下载
        public async Task DownloadFileAsync(string bucketName, string s3Key, string localFilePath)
        {
            try
            {


                var metadata = await GetObjectMetadata(bucketName, s3Key);
                _totalFileSize = metadata.ContentLength;
                _totalDownloadedBytes = 0;
                _totalMergedBytes = 0;

                if (_totalFileSize > DownloadMultipartThreshold)
                {
                    await DownloadLargeFile(bucketName, s3Key, localFilePath, _totalFileSize);
                }
                else
                {
                    await DownloadSmallFile(bucketName, s3Key, localFilePath);
                }

                ReportProgress(TransferPhase.Downloading, 100, "下载完成");
                OnFinish?.Invoke();
            }
            catch (Exception ex)
            {
                if (File.Exists(localFilePath)) File.Delete(localFilePath);
                {
                    //NLogHelper.Log(logger, LogLevel.Error, $"DownloadFileAsync error:${ex.ToString()}");
                    throw new Exception("文件下载失败", ex);
                }
            }
        }

        private async Task DownloadLargeFile(
       string bucketName,
       string s3Key,
       string localFilePath,
       long fileSize)
        {
            var tempFiles = new ConcurrentBag<string>();
            var cts = new CancellationTokenSource();
            var progressCts = new CancellationTokenSource(); // 独立进度取消令牌

            try
            {
                // 计算分块参数
                var partSize = DownloadPartSizeMB * 1024 * 1024;
                var partCount = (int)Math.Ceiling((double)fileSize / partSize);



                // 启动进度报告
                var progressTask = RunProgressReporter(progressCts.Token);

                // 创建并发下载任务
                var downloadTasks = new List<Task>();
                for (var partNumber = 1; partNumber <= partCount; partNumber++)
                {
                    var currentPart = partNumber;
                    downloadTasks.Add(Task.Run(async () =>
                    {
                        await _downloadSemaphore.WaitAsync(cts.Token);
                        try
                        {
                            var partFilePath = $"{localFilePath}.part{currentPart}";
                            if (File.Exists(partFilePath)) File.Delete(partFilePath);

                            await DownloadPartWithRetryAsync(
                                bucketName, s3Key, partFilePath,
                                (currentPart - 1) * partSize,
                                Math.Min(currentPart * partSize - 1, fileSize - 1),
                                cts.Token);

                            tempFiles.Add(partFilePath);
                        }
                        finally
                        {
                            _downloadSemaphore.Release();
                        }
                    }, cts.Token));
                }

                // 并行监控
                await Task.WhenAll(downloadTasks);
                progressCts.CancelAfter(1000); // 延迟取消进度报告

                // 合并阶段
                await ExecuteMergePhase(tempFiles.ToList(), localFilePath, fileSize);
            }
            finally
            {
                // 确保最终进度触发
                ReportProgress(TransferPhase.Downloading, 100, "下载完成");
                cts.Cancel();
                progressCts.Cancel();
                CleanupTempFiles(tempFiles.ToList());
                cts.Dispose();
                progressCts.Dispose();
            }
        }


        private async Task DownloadPartWithRetryAsync(
        string bucketName,
        string s3Key,
        string partFilePath,
        long startByte,
        long endByte,
        CancellationToken cancellationToken)
        {
            var policy = Polly.Policy
                .Handle<AmazonS3Exception>(ex => IsRetryableError(ex))
                .Or<IOException>()
                .WaitAndRetryAsync(MaxRetries, _ => RetryDelay);

            await policy.ExecuteAsync(async () =>
            {
                using var response = await _s3Client.GetObjectAsync(new GetObjectRequest
                {
                    BucketName = bucketName,
                    Key = s3Key,
                    ByteRange = new ByteRange(startByte, endByte)
                }, cancellationToken);

                await using var fileStream = new FileStream(
                    partFilePath,
                    FileMode.Create,
                    FileAccess.Write,
                    FileShare.None,
                    bufferSize: DownloadBufferSize,
                    useAsync: true);

                var buffer = new byte[DownloadBufferSize];
                long totalRead = 0;

                while (true)
                {
                    var bytesRead = await response.ResponseStream.ReadAsync(buffer, cancellationToken);
                    if (bytesRead == 0) break;

                    await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
                    totalRead += bytesRead;

                    // 更新下载进度
                    Interlocked.Add(ref _totalDownloadedBytes, bytesRead);
                }

                // 验证分块完整性
                if (totalRead != endByte - startByte + 1)
                {
                    throw new InvalidDataException($"分块下载不完整，预期大小: {endByte - startByte + 1}，实际下载: {totalRead}");
                }
            });
        }

        private async Task MergePartFilesWithProgress(List<string> partFiles, string finalPath, long totalSize)
        {
            // 验证临时文件存在
            var validFiles = partFiles
                .Where(File.Exists)
                .OrderBy(p =>
                {
                    var match = Regex.Match(p, @"\.part(\d+)$");
                    return match.Success ? int.Parse(match.Groups[1].Value) : 0;
                })
                .ToList();

            if (validFiles.Count == 0)
                throw new FileNotFoundException("未找到任何分块文件");

            // 创建最终文件流（使用异步写入模式）
            await using var finalStream = new FileStream(
                finalPath,
                FileMode.Create,
                FileAccess.Write,
                FileShare.None,
                bufferSize: DownloadBufferSize,
                useAsync: true);

            var buffer = new byte[DownloadBufferSize];
            long totalWritten = 0;

            foreach (var partFile in validFiles)
            {
                // 验证分块文件有效性
                var fileInfo = new FileInfo(partFile);
                if (fileInfo.Length == 0)
                    throw new InvalidDataException($"空分块文件: {partFile}");

                await using var partStream = new FileStream(
                    partFile,
                    FileMode.Open,
                    FileAccess.Read,
                    FileShare.Read,
                    bufferSize: DownloadBufferSize,
                    useAsync: true);

                int bytesRead;
                while ((bytesRead = await partStream.ReadAsync(buffer)) > 0)
                {
                    // 同步写入保证顺序
                    await finalStream.WriteAsync(buffer.AsMemory(0, bytesRead));
                    totalWritten += bytesRead;

                    // 更新进度（确保在主线程执行）
                    Interlocked.Exchange(ref _totalMergedBytes, totalWritten);
                }
            }

            // 强制刷新并验证
            await finalStream.FlushAsync();
            finalStream.Close(); // 显式关闭流

            if (new FileInfo(finalPath).Length != totalSize)
            {
                throw new InvalidDataException(
                    $"文件大小不一致，预期: {totalSize}，实际: {new FileInfo(finalPath).Length}");
            }
        }


        private async Task DownloadSmallFile(
            string bucketName,
            string s3Key,
            string localFilePath)
        {
            using (var response = await _s3Client.GetObjectAsync(bucketName, s3Key))
            {
                await response.WriteResponseStreamToFileAsync(
                    localFilePath,
                    false,
                    CancellationToken.None);
            }
            DownloadProgressChanged?.Invoke(new TransferProgress
            {
                Phase = TransferPhase.Downloading,
                Percentage = 100,
                Status = "下载完成"
            });
        }

        private void ReportProgress(TransferPhase phase, double percentage, string status)
        {

            DownloadProgressChanged?.Invoke(new TransferProgress
            {
                Phase = phase,
                Percentage = Math.Round(percentage, 2),
                Status = status
            });
        }

        private bool IsRetryableError(AmazonS3Exception ex)
        {
            return ex.ErrorCode == "RequestTimeout" ||
                   ex.StatusCode == HttpStatusCode.ServiceUnavailable ||
                   ex.StatusCode == HttpStatusCode.InternalServerError;
        }

        private void CleanupTempFiles(List<string> tempFiles)
        {
            foreach (var tempFile in tempFiles)
            {
                try
                {
                    if (File.Exists(tempFile))
                        File.Delete(tempFile);
                }
                catch
                {
                    // 记录日志
                    Debug.WriteLine($"无法删除临时文件: {tempFile}");
                }
            }
        }

        // 独立进度报告器
        private async Task RunProgressReporter(CancellationToken ct)
        {
            try
            {
                var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1000)); //
                var lastReportTime = DateTime.MinValue;

                while (await timer.WaitForNextTickAsync(ct))
                {
                    var downloaded = Interlocked.Read(ref _totalDownloadedBytes);
                    var progress = Math.Min(100, downloaded * 100d / _totalFileSize);

                    // 强制报告（即使进度无变化）
                    ReportProgress(TransferPhase.Downloading, progress, $"下载中 ({progress:F1}%)");

                    // 最终进度强制关闭
                    if (progress >= 100) break;
                }
            }
            catch (OperationCanceledException)
            {
                // 正常退出
            }
            finally
            {
                // 确保最终进度触发
                ReportProgress(TransferPhase.Downloading, 100, "下载完成");
            }
        }


        // 异步合并流程
        private async Task ExecuteMergePhase(List<string> tempFiles, string localFilePath, long totalSize)
        {
            var mergeCts = new CancellationTokenSource();
            var mergeProgressTask = RunMergeProgressReporter(mergeCts.Token);

            try
            {
                await MergePartFilesWithProgress(tempFiles, localFilePath, totalSize);
                ReportProgress(TransferPhase.Merging, 100, "合并完成");
            }
            finally
            {
                mergeCts.Cancel();
                mergeCts.Dispose();
            }
        }

        // 独立合并进度报告器
        private async Task RunMergeProgressReporter(CancellationToken ct)
        {
            try
            {
                var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); // 改为1秒间隔
                var lastProgress = 0d;

                while (await timer.WaitForNextTickAsync(ct))
                {
                    // 获取最新合并进度
                    var merged = Interlocked.Read(ref _totalMergedBytes);
                    var progress = Math.Min(100, (double)merged / _totalFileSize * 100);

                    //// 仅当进度变化超过0.1%或间隔达到1秒时报告
                    //if (Math.Abs(progress - lastProgress) > 0.1 || progress >= 100)
                    //{
                    //    ReportProgress(TransferPhase.Merging, progress, "合并中");
                    //    lastProgress = progress;
                    //}

                    ReportProgress(TransferPhase.Merging, progress, $"合并中 {progress}");
                    lastProgress = progress;

                    if (progress >= 100) break;
                }
            }
            catch (TaskCanceledException)
            {
                // 正常取消处理
            }
            finally
            {
                // 确保最终进度触发
                ReportProgress(TransferPhase.Merging, 100, "合并完成");
            }
        }

        #endregion


    }
}
